use std::ptr;

struct SListEntry {
    data: *mut libc::c_void,
    next: *mut SListEntry,
}

impl SListEntry {
    fn new(data: *mut libc::c_void) -> Self {
        SListEntry { data, next: ptr::null_mut() }
    }
}

struct SListIterator {
    current: *mut SListEntry,
    previous: *mut SListEntry,
    list: *mut SListEntry,
}

impl SListIterator {
    fn new(list: *mut SListEntry) -> Self {
        SListIterator { current: list, previous: ptr::null_mut(), list }
    }

    fn has_more(&self) -> bool {
        !self.current.is_null()
    }

    fn next(&mut self) -> *mut libc::c_void {
        if self.has_more() {
            unsafe {
                let data = (*self.current).data;
                self.previous = self.current;
                self.current = (*self.current).next;
                data
            }
        } else {
            ptr::null_mut()
        }
    }

    fn remove(&mut self) {
        if !self.previous.is_null() && !self.current.is_null() {
            unsafe {
                (*self.previous).next = (*self.current).next;
                libc::free(self.current as *mut _);
                self.current = (*self.previous).next;
            }
        }
    }
}

fn slist_append(list: *mut SListEntry, data: *mut libc::c_void) -> *mut SListEntry {
    let new_entry = Box::into_raw(Box::new(SListEntry::new(data)));
    if list.is_null() {
        new_entry
    } else {
        unsafe {
            let mut current = list;
            while !(*current).next.is_null() {
                current = (*current).next;
            }
            (*current).next = new_entry;
        }
        list
    }
}

fn slist_prepend(list: *mut SListEntry, data: *mut libc::c_void) -> *mut SListEntry {
    let new_entry = Box::into_raw(Box::new(SListEntry::new(data)));
    unsafe { (*new_entry).next = list; }
    new_entry
}

fn slist_free(mut list: *mut SListEntry) {
    while !list.is_null() {
        unsafe {
            let next = (*list).next;
            libc::free(list as *mut _);
            list = next;
        }
    }
}

fn slist_next(entry: *mut SListEntry) -> *mut SListEntry {
    if entry.is_null() {
        ptr::null_mut()
    } else {
        unsafe { (*entry).next }
    }
}

fn slist_nth_entry(list: *mut SListEntry, n: libc::c_uint) -> *mut SListEntry {
    let mut current = list;
    for _ in 0..n {
        if current.is_null() {
            break;
        }
        unsafe { current = (*current).next; }
    }
    current
}

fn slist_nth_data(list: *mut SListEntry, n: libc::c_uint) -> *mut libc::c_void {
    let entry = slist_nth_entry(list, n);
    if entry.is_null() {
        ptr::null_mut()
    } else {
        unsafe { (*entry).data }
    }
}

fn slist_length(mut list: *mut SListEntry) -> libc::c_uint {
    let mut length = 0;
    while !list.is_null() {
        length += 1;
        unsafe { list = (*list).next; }
    }
    length
}

fn slist_remove_entry(list: *mut SListEntry, entry: *mut SListEntry) -> *mut SListEntry {
    if list.is_null() || entry.is_null() {
        return list;
    }

    let mut current = list;
    let mut previous: *mut SListEntry = ptr::null_mut();

    while !current.is_null() && current != entry {
        unsafe { previous = current; }
        unsafe { current = (*current).next; }
    }

    if current == entry {
        if previous.is_null() {
            unsafe { list = (*list).next; }
        } else {
            unsafe { (*previous).next = (*entry).next; }
        }
        unsafe { libc::free(entry as *mut _); }
    }

    list
}

fn slist_remove_data(list: *mut SListEntry, compare: extern "C" fn(*const libc::c_void, *const libc::c_void) -> libc::c_int, data: *const libc::c_void) -> *mut SListEntry {
    if list.is_null() || data.is_null() {
        return list;
    }

    let mut current = list;
    let mut previous: *mut SListEntry = ptr::null_mut();

    while !current.is_null() && unsafe { compare((*current).data, data) } != 0 {
        unsafe { previous = current; }
        unsafe { current = (*current).next; }
    }

    if !current.is_null() {
        if previous.is_null() {
            unsafe { list = (*list).next; }
        } else {
            unsafe { (*previous).next = (*entry).next; }
        }
        unsafe { libc::free(current as *mut _); }
    }

    list
}

fn slist_iterate(list: *mut SListEntry) -> SListIterator {
    SListIterator::new(list)
}
